// 版权所有2011 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package parse

import (
	"fmt"
	"strings"
	"unicode"
	"unicode/utf8"
)

// 项表示从扫描仪返回的令牌或文本字符串。
type item struct {
	typ  itemType // 此项目的类型。
	pos  Pos      // 此项在输入字符串中的起始位置，以字节为单位。
	val  string   // 此项的值。
	line int      // 此项开头的行号。
}

func (i item) String() string {
	switch {
	case i.typ == itemEOF:
		return "EOF"
	case i.typ == itemError:
		return i.val
	case i.typ > itemKeyword:
		return fmt.Sprintf("<%s>", i.val)
	case len(i.val) > 10:
		return fmt.Sprintf("%.10q...", i.val)
	}
	return fmt.Sprintf("%q", i.val)
}

// itemType标识lex项的类型。
type itemType int

const (
	itemError        itemType = iota // 发生错误；值是错误
	itemBool                         // 布尔常量
	itemChar                         // 可打印ASCII字符的文本；抓取逗号等的袋子
	itemCharConstant                 // 字符常量
	itemComment                      // 注释文本
	itemComplex                      // 复合常量（1+2i）；虚数只是一个数字
	itemAssign                       // equals（'='）引入赋值
	itemDeclare                      // colon equals（':='）引入声明
	itemEOF
	itemField      // 以“.”开头的字母数字标识符
	itemIdentifier // 不以“.”开头的字母数字标识符
	itemLeftDelim  // 左操作分隔符
	itemLeftParen  // ”（'inside action 
	itemNumber     // 简单数字，包括虚构的
	itemPipe       // 管道符号
	itemRawString  // 原始带引号字符串（包括引号）
	itemRightDelim // 右操作分隔符
	itemRightParen // '）inside action 
	itemSpace      // 分隔参数的空格运行
	itemString     // 带引号的字符串（包括报价）
	itemText       // 纯文本
	itemVariable   // 以“$”开头的变量，如“$”或“$1”或“$hello”
	// 关键字出现在所有其他关键字之后。
	itemKeyword  // 仅用于分隔关键字
	itemBlock    // 阻止关键字
	itemDot      // 光标，拼写为“.”
	itemDefine   // 定义关键字
	itemElse     // else关键字
	itemEnd      // end关键字
	itemIf       // if关键字
	itemNil      // 非类型化的零常量，最容易作为关键字
	itemRange    // 范围关键字
	itemTemplate // 模板关键字
	itemWith     // 带关键字
)

var key = map[string]itemType{
	".":        itemDot,
	"block":    itemBlock,
	"define":   itemDefine,
	"else":     itemElse,
	"end":      itemEnd,
	"if":       itemIf,
	"range":    itemRange,
	"nil":      itemNil,
	"template": itemTemplate,
	"with":     itemWith,
}

const eof = -1

// 修剪空格。
// 如果操作需要在“{{-”，而不是“{-”，则操作之前的所有空格/制表符/换行符
// 都会被修剪；相反，如果结束“-}}”，则
// 前导空格会被修剪。这完全是在lexer中完成的；
// 解析器永远不会看到它发生。我们需要一个ASCII空格（“”、\t、\r、\n）
// 出席会议以避免与“{-3}”之类的内容产生歧义.
// 最好是有空格的。为简单起见，只有ASCII 
// 可以完成此任务。
const (
	spaceChars    = " \t\r\n"  // 这些是Go本身定义的空格字符。
	trimMarker    = '-'        // 附加到左/右分隔符，从前面/后面的文本中修剪尾随空格。
	trimMarkerLen = Pos(1 + 1) // marker plus space before or after 
)

// stateFn将扫描程序的状态表示为返回下一个状态的函数。
type stateFn func(*lexer) stateFn

// lexer保存扫描程序的状态。
type lexer struct {
	name        string    // 输入的名称；仅用于错误报告
	input       string    // 正在扫描的字符串
	leftDelim   string    // 开始操作
	rightDelim  string    // 操作结束
	emitComment bool      // 发出itemComment标记。
	pos         Pos       // 输入中的当前位置
	start       Pos       // 此项的开始位置
	width       Pos       // 从输入读取的最后一个符文的宽度
	items       chan item // 扫描项的通道
	parenDepth  int       // 嵌套深度（）exprs 
	line        int       // 1+看到的换行数
	startLine   int       // 此项的起始行
}

// next返回输入中的下一个符文。
func (l *lexer) next() rune {
	if int(l.pos) >= len(l.input) {
		l.width = 0
		return eof
	}
	r, w := utf8.DecodeRuneInString(l.input[l.pos:])
	l.width = Pos(w)
	l.pos += l.width
	if r == '\n' {
		l.line++
	}
	return r
}

// peek返回但不使用输入中的下一个符文。
func (l *lexer) peek() rune {
	r := l.next()
	l.backup()
	return r
}

// 备份一个符文。每次调用next只能调用一次。
func (l *lexer) backup() {
	l.pos -= l.width
	// 正确的换行计数。
	if l.width == 1 && l.input[l.pos] == '\n' {
		l.line--
	}
}

// emit将项传递回客户端。
func (l *lexer) emit(t itemType) {
	l.items <- item{t, l.start, l.input[l.start:l.pos], l.startLine}
	l.start = l.pos
	l.startLine = l.line
}

// ignore跳过此点之前的挂起输入。
func (l *lexer) ignore() {
	l.line += strings.Count(l.input[l.start:l.pos], "\n")
	l.start = l.pos
	l.startLine = l.line
}

// accept使用下一个符文（如果它来自有效集）。
func (l *lexer) accept(valid string) bool {
	if strings.ContainsRune(valid, l.next()) {
		return true
	}
	l.backup()
	return false
}

// acceptRun使用有效集中的一段符文。
func (l *lexer) acceptRun(valid string) {
	for strings.ContainsRune(valid, l.next()) {
	}
	l.backup()
}

// error retur返回一个错误标记，并通过返回一个将成为下一个状态的nil指针来终止扫描，终止l.nextItem.
func (l *lexer) errorf(format string, args ...interface{}) stateFn {
	l.items <- item{itemError, l.start, fmt.Sprintf(format, args...), l.startLine}
	return nil
}

// nextItem返回输入中的下一项。
// 由解析器调用，而不是在lexing goroutine中。
func (l *lexer) nextItem() item {
	return <-l.items
}

// 耗尽输出，以便lexing goroutine将退出。
// 由解析器调用，而不是在lexing goroutine中。
func (l *lexer) drain() {
	for range l.items {
	}
}

// lex为输入字符串创建一个新的扫描程序。
func lex(name, input, left, right string, emitComment bool) *lexer {
	if left == "" {
		left = leftDelim
	}
	if right == "" {
		right = rightDelim
	}
	l := &lexer{
		name:        name,
		input:       input,
		leftDelim:   left,
		rightDelim:  right,
		emitComment: emitComment,
		items:       make(chan item),
		line:        1,
		startLine:   1,
	}
	go l.run()
	return l
}

// run为lexer运行状态机。
func (l *lexer) run() {
	for state := lexText; state != nil; {
		state = state(l)
	}
	close(l.items)
}

// 状态函数

const (
	leftDelim    = "{{"
	rightDelim   = "}}"
	leftComment  = "/*"
	rightComment = "*/"
)

// lexText扫描到打开操作分隔符“{{”.
func lexText(l *lexer) stateFn {
	l.width = 0
	if x := strings.Index(l.input[l.pos:], l.leftDelim); x >= 0 {
		ldn := Pos(len(l.leftDelim))
		l.pos += Pos(x)
		trimLength := Pos(0)
		if hasLeftTrimMarker(l.input[l.pos+ldn:]) {
			trimLength = rightTrimLength(l.input[l.start:l.pos])
		}
		l.pos -= trimLength
		if l.pos > l.start {
			l.line += strings.Count(l.input[l.start:l.pos], "\n")
			l.emit(itemText)
		}
		l.pos += trimLength
		l.ignore()
		return lexLeftDelim
	}
	l.pos = Pos(len(l.input))
	// 正确到达EOF.
	if l.pos > l.start {
		l.line += strings.Count(l.input[l.start:l.pos], "\n")
		l.emit(itemText)
	}
	l.emit(itemEOF)
	return nil
}

// rightTrimLength返回字符串末尾空格的长度。
func rightTrimLength(s string) Pos {
	return Pos(len(s) - len(strings.TrimRight(s, spaceChars)))
}

// atRightDelim报告lexer是否位于右分隔符处，前面可能有修剪标记。
func (l *lexer) atRightDelim() (delim, trimSpaces bool) {
	if hasRightTrimMarker(l.input[l.pos:]) && strings.HasPrefix(l.input[l.pos+trimMarkerLen:], l.rightDelim) { // 带修剪标记。
		return true, true
	}
	if strings.HasPrefix(l.input[l.pos:], l.rightDelim) { // 不带修剪标记。
		return true, false
	}
	return false, false
}

// leftTrimLength返回字符串开头空格的长度。
func leftTrimLength(s string) Pos {
	return Pos(len(s) - len(strings.TrimLeft(s, spaceChars)))
}

// lexLeftDelim扫描已知存在的左分隔符，可能带有修剪标记。
func lexLeftDelim(l *lexer) stateFn {
	l.pos += Pos(len(l.leftDelim))
	trimSpace := hasLeftTrimMarker(l.input[l.pos:])
	afterMarker := Pos(0)
	if trimSpace {
		afterMarker = trimMarkerLen
	}
	if strings.HasPrefix(l.input[l.pos+afterMarker:], leftComment) {
		l.pos += afterMarker
		l.ignore()
		return lexComment
	}
	l.emit(itemLeftDelim)
	l.pos += afterMarker
	l.ignore()
	l.parenDepth = 0
	return lexInsideAction
}

// lexComment扫描注释。已知存在左注释标记。
func lexComment(l *lexer) stateFn {
	l.pos += Pos(len(leftComment))
	i := strings.Index(l.input[l.pos:], rightComment)
	if i < 0 {
		return l.errorf("unclosed comment")
	}
	l.pos += Pos(i + len(rightComment))
	delim, trimSpace := l.atRightDelim()
	if !delim {
		return l.errorf("comment ends before closing delimiter")
	}
	if l.emitComment {
		l.emit(itemComment)
	}
	if trimSpace {
		l.pos += trimMarkerLen
	}
	l.pos += Pos(len(l.rightDelim))
	if trimSpace {
		l.pos += leftTrimLength(l.input[l.pos:])
	}
	l.ignore()
	return lexText
}

// lexRightDelim扫描右分隔符，已知存在，可能带有修剪标记。
func lexRightDelim(l *lexer) stateFn {
	trimSpace := hasRightTrimMarker(l.input[l.pos:])
	if trimSpace {
		l.pos += trimMarkerLen
		l.ignore()
	}
	l.pos += Pos(len(l.rightDelim))
	l.emit(itemRightDelim)
	if trimSpace {
		l.pos += leftTrimLength(l.input[l.pos:])
		l.ignore()
	}
	return lexText
}

// lexInsideAction扫描操作分隔符内的元素。
func lexInsideAction(l *lexer) stateFn {
	// 数字、带引号的字符串或标识符。
	// 分隔单独的参数；空格的运行转换为itemSpace。
	// 管道符号分开并发出。
	delim, _ := l.atRightDelim()
	if delim {
		if l.parenDepth == 0 {
			return lexRightDelim
		}
		return l.errorf("unclosed left paren")
	}
	switch r := l.next(); {
	case r == eof:
		return l.errorf("unclosed action")
	case isSpace(r):
		l.backup() // 如果有“-}”，请将空格放回原处。
		return lexSpace
	case r == '=':
		l.emit(itemAssign)
	case r == ':':
		if l.next() != '=' {
			return l.errorf("expected :=")
		}
		l.emit(itemDeclare)
	case r == '|':
		l.emit(itemPipe)
	case r == '"':
		return lexQuote
	case r == '`':
		return lexRawQuote
	case r == '$':
		return lexVariable
	case r == '\'':
		return lexChar
	case r == '.':
		// 对“.field”进行特殊的前瞻性检查，这样我们就不会破坏l.backup（）。
		if l.pos < Pos(len(l.input)) {
			r := l.input[l.pos]
			if r < '0' || '9' < r {
				return lexField
			}
		}
		fallthrough // /'.'可以开始一个数字。
	case r == '+' || r == '-' || ('0' <= r && r <= '9'):
		l.backup()
		return lexNumber
	case isAlphaNumeric(r):
		l.backup()
		return lexIdentifier
	case r == '(':
		l.emit(itemLeftParen)
		l.parenDepth++
	case r == ')':
		l.emit(itemRightParen)
		l.parenDepth--
		if l.parenDepth < 0 {
			return l.errorf("unexpected right paren %#U", r)
		}
	case r <= unicode.MaxASCII && unicode.IsPrint(r):
		l.emit(itemChar)
	default:
		return l.errorf("unrecognized character in action: %#U", r)
	}
	return lexInsideAction
}

// lexSpace扫描一系列空格字符。
// 我们尚未使用已知存在的第一个空格。
// 如果有以空格开头的右分隔符，请小心。
func lexSpace(l *lexer) stateFn {
	var r rune
	var numSpaces int
	for {
		r = l.peek()
		if !isSpace(r) {
			break
		}
		l.next()
		numSpaces++
	}
	// 注意带有修剪标记的结束分隔符，它在空格后有一个负号
	// 。我们知道有一个空格，所以检查后面可能出现的“-”。
	if hasRightTrimMarker(l.input[l.pos-1:]) && strings.HasPrefix(l.input[l.pos-1+trimMarkerLen:], l.rightDelim) {
		l.backup() // 在空格前。
		if numSpaces == 1 {
			return lexRightDelim // 在熟食店，直接去吧。
		}
	}
	l.emit(itemSpace)
	return lexInsideAction
}

// lexIdentifier扫描字母数字。
func lexIdentifier(l *lexer) stateFn {
Loop:
	for {
		switch r := l.next(); {
		case isAlphaNumeric(r):
			// 吸收。
		default:
			l.backup()
			word := l.input[l.start:l.pos]
			if !l.atTerminator() {
				return l.errorf("bad character %#U", r)
			}
			switch {
			case key[word] > itemKeyword:
				l.emit(key[word])
			case word[0] == '.':
				l.emit(itemField)
			case word == "true", word == "false":
				l.emit(itemBool)
			default:
				l.emit(itemIdentifier)
			}
			break Loop
		}
	}
	return lexInsideAction
}

// lexField扫描字段：。字母数字。
// 该文件。已经扫描过了。
func lexField(l *lexer) stateFn {
	return lexFieldOrVariable(l, itemField)
}

// lexVariable扫描变量：$字母数字。
// 美元已被扫描。
func lexVariable(l *lexer) stateFn {
	if l.atTerminator() { // 后面没有什么有趣的东西->“$”。
		l.emit(itemVariable)
		return lexInsideAction
	}
	return lexFieldOrVariable(l, itemVariable)
}

// lexVariable扫描字段或变量：[.$]字母数字。
// 该文件。或美元已被扫描。
func lexFieldOrVariable(l *lexer, typ itemType) stateFn {
	if l.atTerminator() { // 没有任何有趣的内容跟随->“”或“$”。
		if typ == itemVariable {
			l.emit(itemVariable)
		} else {
			l.emit(itemDot)
		}
		return lexInsideAction
	}
	var r rune
	for {
		r = l.next()
		if !isAlphaNumeric(r) {
			l.backup()
			break
		}
	}
	if !l.atTerminator() {
		return l.errorf("bad character %#U", r)
	}
	l.emit(typ)
	return lexInsideAction
}

// atTerminator报告输入是否在有效的终止字符处，以
// 显示在标识符之后。将.X.Y分成两部分。还捕获了
// 的情况，如“$x+2”没有空格是不可接受的，以防我们决定在一天内实现算法。
func (l *lexer) atTerminator() bool {
	r := l.peek()
	if isSpace(r) {
		return true
	}
	switch r {
	case eof, '.', ',', '|', ':', ')', '(':
		return true
	}
	// r是否开始分隔符？这可能是不明确的（delim==“
	// 成功，但应该失败）但只有在极少数情况下，由于故意使用了错误的分隔符导致。
	// lexChar扫描字符常量。初始引号已被
	if rd, _ := utf8.DecodeRuneInString(l.rightDelim); rd == r {
		return true
	}
	return false
}

// 扫描。语法检查由解析器完成。
func lexChar(l *lexer) stateFn {
Loop:
	for {
		switch l.next() {
		case '\\':
			if r := l.next(); r != eof && r != '\n' {
				break
			}
			fallthrough
		case eof, '\n':
			return l.errorf("unterminated character constant")
		case '\'':
			break Loop
		}
	}
	l.emit(itemCharConstant)
	return lexInsideAction
}

// lexNumber扫描数字：十进制、八进制、十六进制、浮点或虚值。此
// 不是一个完美的数字扫描器-例如，它接受“.”和“0x0.2”
// 和“089”-但如果输入错误，则输入无效，解析器（通过
// strconv）威尔注意到。
func lexNumber(l *lexer) stateFn {
	if !l.scanNumber() {
		return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
	}
	if sign := l.peek(); sign == '+' || sign == '-' {
		// 复数：1+2i。没有空格，必须以“i”结尾。
		if !l.scanNumber() || l.input[l.pos-1] != 'i' {
			return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
		}
		l.emit(itemComplex)
	} else {
		l.emit(itemNumber)
	}
	return lexInsideAction
}

func (l *lexer) scanNumber() bool {
	// 可选的前导符号。
	l.accept("+-")
	// 是十六进制吗？
	digits := "0123456789_"
	if l.accept("0") {
		// 注意：前导0在浮点数中不是八进制。
		if l.accept("xX") {
			digits = "0123456789abcdefABCDEF_"
		} else if l.accept("oO") {
			digits = "01234567_"
		} else if l.accept("bB") {
			digits = "01_"
		}
	}
	l.acceptRun(digits)
	if l.accept(".") {
		l.acceptRun(digits)
	}
	if len(digits) == 10+1 && l.accept("eE") {
		l.accept("+-")
		l.acceptRun("0123456789_")
	}
	if len(digits) == 16+6+1 && l.accept("pP") {
		l.accept("+-")
		l.acceptRun("0123456789_")
	}
	// 是虚构的吗？
	l.accept("i")
	// 下一件事不能是字母数字的。
	if isAlphaNumeric(l.peek()) {
		l.next()
		return false
	}
	return true
}

// lexQuote扫描带引号的字符串。
func lexQuote(l *lexer) stateFn {
Loop:
	for {
		switch l.next() {
		case '\\':
			if r := l.next(); r != eof && r != '\n' {
				break
			}
			fallthrough
		case eof, '\n':
			return l.errorf("unterminated quoted string")
		case '"':
			break Loop
		}
	}
	l.emit(itemString)
	return lexInsideAction
}

// lexRawQuote扫描带引号的原始字符串。
func lexRawQuote(l *lexer) stateFn {
Loop:
	for {
		switch l.next() {
		case eof:
			return l.errorf("unterminated raw quoted string")
		case '`':
			break Loop
		}
	}
	l.emit(itemRawString)
	return lexInsideAction
}

// isSpace报告r是否为空格字符。
func isSpace(r rune) bool {
	return r == ' ' || r == '\t' || r == '\r' || r == '\n'
}

// isAlphaNumeric报告r是否为字母、数字或下划线。
func isAlphaNumeric(r rune) bool {
	return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
}

func hasLeftTrimMarker(s string) bool {
	return len(s) >= 2 && s[0] == trimMarker && isSpace(rune(s[1]))
}

func hasRightTrimMarker(s string) bool {
	return len(s) >= 2 && isSpace(rune(s[0])) && s[1] == trimMarker
}
